﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net;
using System.Net.Mail;
using PSE.Framework.Configuration;
using System.Configuration;
using PSE.Framework.Mail.Configuration;
using System.Xml.Serialization;
using System.Xml.Xsl;
using System.Xml;
using System.IO;
using PSE.Framework.Cryptography.Util;

namespace PSE.Framework.Mail
{
    public class MailClient : SmtpClient
    {
        private const string SECTION_NAME = "mailConfiguration";
        private ApplicationConfiguration _applicationConfiguration;
        private MailConfigurationSection _mailConfiguration;
        private MailAddresConfiguration _fromAddressConfiguration = null;

        public MailClient()
            : this(null, 0, null)
        {
        }

        public MailClient(NetworkCredential credential)
            : this(null, 0, credential)
        {
        }

        public MailClient(string host, int port)
            : this(host, port, null)
        {
        }

        public MailClient(string host, int port, NetworkCredential credential)
            : base(host, port)
        {
            _applicationConfiguration = ApplicationConfiguration.GetCurrent();
            _mailConfiguration = _applicationConfiguration.GetSection<MailConfigurationSection>(SECTION_NAME);

            bool enableSsl = false;

            if (_mailConfiguration != null)
            {
                if (_mailConfiguration.From != null && !string.IsNullOrEmpty(_mailConfiguration.From.Address))
                    _fromAddressConfiguration = _mailConfiguration.From;

                //se nao informou as configuracoes de host e port, recuperar do config
                if (string.IsNullOrEmpty(host) && port == 0)
                {
                    if (_mailConfiguration.SmtpServer == null ||
                        string.IsNullOrEmpty(_mailConfiguration.SmtpServer.Host) ||
                        _mailConfiguration.SmtpServer.Port == 0)
                        throw new ConfigurationErrorsException("The StmpServer Host and Port must be configured");

                    host = _mailConfiguration.SmtpServer.Host;
                    port = _mailConfiguration.SmtpServer.Port;
                    enableSsl = _mailConfiguration.SmtpServer.EnableSsl;
                }
            }

            this.Host = host;
            this.Port = port;
            this.EnableSsl = enableSsl;

            //se nao informou uma credencial
            if (credential == null)
            {
                //se ha credencial configurada
                if (_mailConfiguration != null &&
                    _mailConfiguration.NetworkCredential != null &&
                    !string.IsNullOrEmpty(_mailConfiguration.NetworkCredential.UserName))
                {
                    string password = string.Empty;
                    if (!string.IsNullOrEmpty(_mailConfiguration.NetworkCredential.Password))
                        password = DESCryptographer.Decrypt(_mailConfiguration.NetworkCredential.Password, GetCryptoKey());

                    if (!string.IsNullOrEmpty(_mailConfiguration.NetworkCredential.Domain))
                    {
                        this.Credentials = new NetworkCredential(
                            _mailConfiguration.NetworkCredential.UserName,
                            password,
                            _mailConfiguration.NetworkCredential.Domain);
                    }
                    else
                    {
                        this.Credentials = new NetworkCredential(
                            _mailConfiguration.NetworkCredential.UserName,
                            password);
                    }
                }
                else
                    this.UseDefaultCredentials = true;
            }
            else
                this.Credentials = credential;
        }

        private byte[] GetCryptoKey()
        {
            byte[] keyCopy = new byte[Constants.CRYPTO_KEY.Length];
            Constants.CRYPTO_KEY.CopyTo(keyCopy, 0);
            return keyCopy;
        }

        //
        // Summary:
        //     Sends the specified message to an SMTP server for delivery.
        //
        // Parameters:
        //   message:
        //     A System.Net.Mail.MailMessage that contains the message to send.
        //
        // Exceptions:
        //   System.ArgumentNullException:
        //     System.Net.Mail.MailMessage.From is null.  -or- System.Net.Mail.MailMessage.To
        //     is null.  -or- message is null.
        //
        //   System.ArgumentOutOfRangeException:
        //     There are no recipients in System.Net.Mail.MailMessage.To, System.Net.Mail.MailMessage.CC,
        //     and System.Net.Mail.MailMessage.Bcc.
        //
        //   System.InvalidOperationException:
        //     This System.Net.Mail.SmtpClient has a Overload:System.Net.Mail.SmtpClient.SendAsync
        //     call in progress.  -or- System.Net.Mail.SmtpClient.Host is null.  -or- System.Net.Mail.SmtpClient.Host
        //     is equal to the empty string ("").  -or- System.Net.Mail.SmtpClient.Port
        //     is zero.
        //
        //   System.ObjectDisposedException:
        //     This object has been disposed.
        //
        //   System.Net.Mail.SmtpException:
        //     The connection to the SMTP server failed.  -or- Authentication failed.  -or-
        //     The operation timed out.
        //
        //   System.Net.Mail.SmtpFailedRecipientsException:
        //     The message could not be delivered to one or more of the recipients in System.Net.Mail.MailMessage.To,
        //     System.Net.Mail.MailMessage.CC, or System.Net.Mail.MailMessage.Bcc.
        public new void Send(MailMessage message)
        {
            if (message.From == null && _fromAddressConfiguration != null && !string.IsNullOrEmpty(_fromAddressConfiguration.Address))
                message.From = new MailAddress(_fromAddressConfiguration.Address, string.IsNullOrEmpty(_fromAddressConfiguration.DisplayName) ? _fromAddressConfiguration.Address : _fromAddressConfiguration.DisplayName);

            base.Send(message);
        }

        //
        // Summary:
        //     Sends the specified e-mail message to an SMTP server for delivery. The message
        //     sender, recipients, subject, and message body are specified using System.String
        //     objects.
        //
        // Parameters:
        //   from:
        //     A System.String that contains the address information of the message sender.
        //
        //   recipients:
        //     A System.String that contains the addresses that the message is sent to.
        //
        //   subject:
        //     A System.String that contains the subject line for the message.
        //
        //   body:
        //     A System.String that contains the message body.
        //
        // Exceptions:
        //   System.ArgumentNullException:
        //     from is null.  -or- recipient is null.
        //
        //   System.ArgumentException:
        //     from is System.String.Empty.  -or- recipient is System.String.Empty.
        //
        //   System.InvalidOperationException:
        //     This System.Net.Mail.SmtpClient has a Overload:System.Net.Mail.SmtpClient.SendAsync
        //     call in progress.  -or- System.Net.Mail.SmtpClient.Host is null.  -or- System.Net.Mail.SmtpClient.Host
        //     is equal to the empty string ("").  -or- System.Net.Mail.SmtpClient.Port
        //     is zero.
        //
        //   System.ObjectDisposedException:
        //     This object has been disposed.
        //
        //   System.Net.Mail.SmtpException:
        //     The connection to the SMTP server failed.  -or- Authentication failed.  -or-
        //     The operation timed out.
        //
        //   System.Net.Mail.SmtpFailedRecipientsException:
        //     The message could not be delivered to one or more of the recipients in recipients.
        public new void Send(string from, string recipients, string subject, string body)
        {
            if (string.IsNullOrEmpty(from) && _fromAddressConfiguration != null && !string.IsNullOrEmpty(_fromAddressConfiguration.Address))
                from = new MailAddress(_fromAddressConfiguration.Address, string.IsNullOrEmpty(_fromAddressConfiguration.DisplayName) ? _fromAddressConfiguration.Address : _fromAddressConfiguration.DisplayName).ToString();

            base.Send(from, recipients, subject, body);
        }

        //
        // Summary:
        //     Sends the specified e-mail message to an SMTP server for delivery. This method
        //     does not block the calling thread and allows the caller to pass an object
        //     to the method that is invoked when the operation completes.
        //
        // Parameters:
        //   message:
        //     A System.Net.Mail.MailMessage that contains the message to send.
        //
        //   userToken:
        //     A user-defined object that is passed to the method invoked when the asynchronous
        //     operation completes.
        //
        // Exceptions:
        //   System.ArgumentNullException:
        //     System.Net.Mail.MailMessage.From is null.  -or- System.Net.Mail.MailMessage.To
        //     is null.  -or- message is null.
        //
        //   System.ArgumentOutOfRangeException:
        //     There are no recipients in System.Net.Mail.MailMessage.To, System.Net.Mail.MailMessage.CC,
        //     and System.Net.Mail.MailMessage.Bcc.
        //
        //   System.InvalidOperationException:
        //     This System.Net.Mail.SmtpClient has a Overload:System.Net.Mail.SmtpClient.SendAsync
        //     call in progress.  -or- System.Net.Mail.SmtpClient.Host is null.  -or- System.Net.Mail.SmtpClient.Host
        //     is equal to the empty string ("").  -or- System.Net.Mail.SmtpClient.Port
        //     is zero.
        //
        //   System.ObjectDisposedException:
        //     This object has been disposed.
        //
        //   System.Net.Mail.SmtpException:
        //     The connection to the SMTP server failed.  -or- Authentication failed.  -or-
        //     The operation timed out.
        //
        //   System.Net.Mail.SmtpFailedRecipientsException:
        //     The message could not be delivered to one or more of the recipients in System.Net.Mail.MailMessage.To,
        //     System.Net.Mail.MailMessage.CC, or System.Net.Mail.MailMessage.Bcc.
        public new void SendAsync(MailMessage message, object userToken)
        {
            if (message.From == null && _fromAddressConfiguration != null && !string.IsNullOrEmpty(_fromAddressConfiguration.Address))
                message.From = new MailAddress(_fromAddressConfiguration.Address, string.IsNullOrEmpty(_fromAddressConfiguration.DisplayName) ? _fromAddressConfiguration.Address : _fromAddressConfiguration.DisplayName);

            base.SendAsync(message, userToken);
        }

        //
        // Summary:
        //     Sends an e-mail message to an SMTP server for delivery. The message sender,
        //     recipients, subject, and message body are specified using System.String objects.
        //     This method does not block the calling thread and allows the caller to pass
        //     an object to the method that is invoked when the operation completes.
        //
        // Parameters:
        //   from:
        //     A System.String that contains the address information of the message sender.
        //
        //   recipients:
        //     A System.String that contains the address that the message is sent to.
        //
        //   subject:
        //     A System.String that contains the subject line for the message.
        //
        //   body:
        //     A System.String that contains the message body.
        //
        //   userToken:
        //     A user-defined object that is passed to the method invoked when the asynchronous
        //     operation completes.
        //
        // Exceptions:
        //   System.ArgumentNullException:
        //     from is null.  -or- recipient is null.
        //
        //   System.ArgumentException:
        //     from is System.String.Empty.  -or- recipient is System.String.Empty.
        //
        //   System.InvalidOperationException:
        //     This System.Net.Mail.SmtpClient has a Overload:System.Net.Mail.SmtpClient.SendAsync
        //     call in progress.  -or- System.Net.Mail.SmtpClient.Host is null.  -or- System.Net.Mail.SmtpClient.Host
        //     is equal to the empty string ("").  -or- System.Net.Mail.SmtpClient.Port
        //     is zero.
        //
        //   System.ObjectDisposedException:
        //     This object has been disposed.
        //
        //   System.Net.Mail.SmtpException:
        //     The connection to the SMTP server failed.  -or- Authentication failed.  -or-
        //     The operation timed out.
        //
        //   System.Net.Mail.SmtpFailedRecipientsException:
        //     The message could not be delivered to one or more of the recipients in recipients.
        public new void SendAsync(string from, string recipients, string subject, string body, object userToken)
        {
            if (string.IsNullOrEmpty(from) && _fromAddressConfiguration != null && !string.IsNullOrEmpty(_fromAddressConfiguration.Address))
                from = new MailAddress(_fromAddressConfiguration.Address, string.IsNullOrEmpty(_fromAddressConfiguration.DisplayName) ? _fromAddressConfiguration.Address : _fromAddressConfiguration.DisplayName).ToString();

            base.SendAsync(from, recipients, subject, body, userToken);
        }

        /// <summary>
        /// Envia uma mensagem via e-mail
        /// </summary>
        /// <param name="message">Mensagem a ser enviada</param>
        /// <param name="bodyTemplate">XmlReader de um arquivo xslt que contém o template do corpo da mensagem</param>
        /// <param name="bodyData">Objeto com os dados a serem inseridos no corpo da mensagem</param>
        public void Send(System.Net.Mail.MailMessage message, XmlReader bodyTemplate, object bodyData)
        {
            if (message == null)
                throw new ArgumentNullException("message");

            if (bodyTemplate == null)
                throw new ArgumentNullException("bodyTemplate");

            if (bodyData == null)
                throw new ArgumentNullException("bodyData");


            XslCompiledTransform transform = new XslCompiledTransform();
            transform.Load(bodyTemplate);

            using (MemoryStream inputMs = new MemoryStream())
            {
                // Serializando objeto
                XmlSerializer serializer = new XmlSerializer(bodyData.GetType());
                serializer.Serialize(inputMs, bodyData);

                inputMs.Position = 0;

                using (XmlReader input = XmlReader.Create(inputMs))
                {
                    StringBuilder resultsSb = new StringBuilder();

                    using (XmlWriter results = XmlWriter.Create(resultsSb, new XmlWriterSettings() { OmitXmlDeclaration = true }))
                    {
                        transform.Transform(input, results);

                        message.Body = resultsSb.ToString();

                        this.Send(message);
                    }
                }
            }
        }
    }
}
